#include <iostream>

#include "arp_message.hh"
#include "exception.hh"
#include "network_interface.hh"

using namespace std;

//! \param[in] ethernet_address Ethernet (what ARP calls "hardware") address of the interface
//! \param[in] ip_address IP (what ARP calls "protocol") address of the interface
NetworkInterface::NetworkInterface( string_view name,
                                    shared_ptr<OutputPort> port,
                                    const EthernetAddress& ethernet_address,
                                    const Address& ip_address )
  : name_( name )
  , port_( notnull( "OutputPort", move( port ) ) )
  , ethernet_address_( ethernet_address )
  , ip_address_( ip_address )
  , current_time_(0)
  , arp_table_()
  , frame_queue_()
{
  cerr << "DEBUG: Network interface has Ethernet address " << to_string( ethernet_address ) << " and IP address "
       << ip_address.ip() << "\n";
}

//! \param[in] dgram the IPv4 datagram to be sent
//! \param[in] next_hop the IP address of the interface to send it to (typically a router or default gateway, but
//! may also be another host if directly connected to the same network as the destination) Note: the Address type
//! can be converted to a uint32_t (raw 32-bit IP address) by using the Address::ipv4_numeric() method.
void NetworkInterface::send_datagram( const InternetDatagram& dgram, const Address& next_hop )
{
  // Your code here.
  EthernetFrame messsage = EthernetFrame();
  const uint32_t target_ip = next_hop.ipv4_numeric();
  messsage.header.src = ethernet_address_;
  messsage.header.type = EthernetHeader::TYPE_IPv4;
  messsage.payload = serialize(dgram);
  if(!arp_table_.contains(target_ip) || arp_table_[target_ip].second < current_time_){
    frame_queue_[target_ip].first.push(std::move(messsage));
    EthernetFrame arp_request_frame;
    send_arp_request( target_ip, arp_request_frame );
    return;
  } else {
    messsage.header.dst = arp_table_[target_ip].first;
    transmit(messsage);
  }
}
void NetworkInterface::send_arp_request( const uint32_t target_ip, EthernetFrame& arp_request_frame )
{
  if(frame_queue_.contains(target_ip) && frame_queue_[target_ip].second.has_value()
       && frame_queue_[target_ip].second >= current_time_)
    return;
  arp_request_frame.header.type = EthernetHeader::TYPE_ARP;
  arp_request_frame.header.dst = ETHERNET_BROADCAST;
  arp_request_frame.header.src = ethernet_address_;
  ARPMessage arp_request_message = ARPMessage();
  arp_request_message.sender_ethernet_address = ethernet_address_;
  arp_request_message.sender_ip_address = ip_address_.ipv4_numeric();
  arp_request_message.opcode = ARPMessage::OPCODE_REQUEST;
  arp_request_message.target_ip_address = target_ip;
//  arp_request_message.target_ethernet_address = ETHERNET_BROADCAST;
  arp_request_frame.payload = serialize(arp_request_message);
  transmit(arp_request_frame);
  frame_queue_[target_ip].second = current_time_ + 5000;
}

//! \param[in] frame the incoming Ethernet frame
void NetworkInterface::recv_frame( const EthernetFrame& frame )
{
  // Your code here.
  if(frame.header.dst == ethernet_address_ || frame.header.dst == ETHERNET_BROADCAST){
    if(frame.header.type == EthernetHeader::TYPE_ARP){
      ARPMessage message = ARPMessage();
      if(parse(message, frame.payload) && message.target_ip_address == ip_address_.ipv4_numeric()) {
        arp_table_[message.sender_ip_address] = make_pair(message.sender_ethernet_address, current_time_+30000);
        if(message.opcode == ARPMessage::OPCODE_REQUEST){
          EthernetFrame response = EthernetFrame();
          make_arp_response( message, response );
          transmit(response);
        } else {
          // 收到arp回复之后看对应ip有无待发送的消息
          queue<EthernetFrame>& ip_queue = frame_queue_[message.sender_ip_address].first;
          while (!ip_queue.empty()){
            ip_queue.front().header.dst = message.sender_ethernet_address;
            transmit(ip_queue.front());
            ip_queue.pop();
          }
        }
      }

    } else if(frame.header.type == EthernetHeader::TYPE_IPv4){
      InternetDatagram message = InternetDatagram();
      if(parse(message, frame.payload)){
        datagrams_received_.emplace(std::move(message));
      }
    }
  }
}
void NetworkInterface::make_arp_response( const ARPMessage& message, EthernetFrame& response ) const
{
  EthernetHeader& header = response.header;
  header.dst = message.sender_ethernet_address;
  header.src = ethernet_address_;
  header.type = EthernetHeader::TYPE_ARP;
  ARPMessage arp_response_message = ARPMessage();
  arp_response_message.opcode = ARPMessage::OPCODE_REPLY;
  arp_response_message.sender_ethernet_address = ethernet_address_;
  arp_response_message.sender_ip_address = ip_address_.ipv4_numeric();
  arp_response_message.target_ethernet_address = message.sender_ethernet_address;
  arp_response_message.target_ip_address = message.sender_ip_address;
  response.payload = serialize(arp_response_message);
  return;
}

//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void NetworkInterface::tick( const size_t ms_since_last_tick )
{
  // Your code here.
  current_time_ += ms_since_last_tick;
}
